// Package bootstrap provides application initialization and startup
package bootstrap

import (
	"fmt"
	"time"

	"gitee.com/zhenyangze/gin-framework/internal/app/providers"
	"gitee.com/zhenyangze/gin-framework/internal/pkg/config"
	"gitee.com/zhenyangze/gin-framework/internal/pkg/container"
	"gitee.com/zhenyangze/gin-framework/internal/pkg/logger"
	"github.com/asaskevich/EventBus"
	"github.com/go-redis/redis"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	gormLogger "gorm.io/gorm/logger"
)

// Bootstrapper handles application initialization
type Bootstrapper struct {
	container *container.Container
	config    config.Manager
	logger    logger.Logger
}

// NewBootstrapper creates a new bootstrapper
func NewBootstrapper(configPath string) (*Bootstrapper, error) {
	// Initialize configuration
	cfg, err := config.NewManager(configPath)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize config: %w", err)
	}

	// Validate configuration
	if err := cfg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid configuration: %w", err)
	}

	// Initialize container
	c := container.NewContainer()
	c.SetConfig(cfg)

	return &Bootstrapper{
		container: c,
		config:    cfg,
	}, nil
}

// Bootstrap initializes all application services
func (b *Bootstrapper) Bootstrap() error {
	// Initialize logger first
	if err := b.initLogger(); err != nil {
		return fmt.Errorf("failed to initialize logger: %w", err)
	}

	b.logger.Info("Starting application bootstrap")

	// Initialize database
	if err := b.initDatabase(); err != nil {
		return fmt.Errorf("failed to initialize database: %w", err)
	}

	// Initialize Redis
	if err := b.initRedis(); err != nil {
		return fmt.Errorf("failed to initialize Redis: %w", err)
	}

	// Initialize event bus
	if err := b.initEventBus(); err != nil {
		return fmt.Errorf("failed to initialize event bus: %w", err)
	}

	// Setup shutdown hooks
	b.setupShutdownHooks()

	// Initialize backward compatibility layer
	b.initCompatibilityLayer()

	b.logger.Info("Application bootstrap completed successfully")
	return nil
}

// initLogger initializes the structured logger
func (b *Bootstrapper) initLogger() error {
	logConfig := &logger.Config{
		Level:      b.config.GetString("log.level"),
		Path:       b.config.GetString("log.path"),
		Name:       b.config.GetString("log.name"),
		MaxSize:    b.config.GetInt("log.max_size"),
		MaxBackups: b.config.GetInt("log.max_backups"),
		MaxAge:     b.config.GetInt("log.max_age"),
		Compress:   b.config.GetBool("log.compress"),
	}

	log, err := logger.NewLogger(logConfig)
	if err != nil {
		return err
	}

	b.logger = log
	b.container.SetLogger(log)
	return nil
}

// initDatabase initializes the database connection
func (b *Bootstrapper) initDatabase() error {
	b.logger.Info("Initializing database connection")

	// Build DSN
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		b.config.GetString("databases.username"),
		b.config.GetString("databases.password"),
		b.config.GetString("databases.host"),
		b.config.GetString("databases.port"),
		b.config.GetString("databases.database"),
	)

	// Configure GORM
	gormConfig := &gorm.Config{
		Logger: gormLogger.Default.LogMode(gormLogger.Silent),
	}

	if b.config.GetBool("databases.debug") {
		gormConfig.Logger = gormLogger.Default.LogMode(gormLogger.Info)
	}

	// Open database connection
	db, err := gorm.Open(mysql.Open(dsn), gormConfig)
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}

	// Configure connection pool
	sqlDB, err := db.DB()
	if err != nil {
		return fmt.Errorf("failed to get underlying sql.DB: %w", err)
	}

	sqlDB.SetMaxOpenConns(b.config.GetInt("databases.max_open_conns"))
	sqlDB.SetMaxIdleConns(b.config.GetInt("databases.max_idle_conns"))
	sqlDB.SetConnMaxLifetime(time.Duration(b.config.GetInt("databases.max_lifetime_conns")) * time.Second)

	// Test connection
	if err := sqlDB.Ping(); err != nil {
		return fmt.Errorf("failed to ping database: %w", err)
	}

	b.container.SetDB(db)
	b.logger.Info("Database connection initialized successfully")

	// Add shutdown hook
	b.container.AddStopHook(func() error {
		b.logger.Info("Closing database connection")
		return sqlDB.Close()
	})

	return nil
}

// initRedis initializes the Redis client
func (b *Bootstrapper) initRedis() error {
	b.logger.Info("Initializing Redis connection")

	client := redis.NewClient(&redis.Options{
		Addr:         b.config.GetString("redis.addr"),
		Password:     b.config.GetString("redis.password"),
		DB:           b.config.GetInt("redis.db"),
		PoolSize:     b.config.GetIntDefault("redis.pool_size", 10),
		MinIdleConns: b.config.GetIntDefault("redis.min_idle_conns", 3),
	})

	// Test connection
	if err := client.Ping().Err(); err != nil {
		return fmt.Errorf("failed to connect to Redis: %w", err)
	}

	b.container.SetRedis(client)
	b.logger.Info("Redis connection initialized successfully")

	// Add shutdown hook
	b.container.AddStopHook(func() error {
		b.logger.Info("Closing Redis connection")
		return client.Close()
	})

	return nil
}

// initEventBus initializes the event bus
func (b *Bootstrapper) initEventBus() error {
	b.logger.Info("Initializing event bus")

	eventBus := EventBus.New()
	b.container.SetEvent(eventBus)

	b.logger.Info("Event bus initialized successfully")

	// Add shutdown hook
	b.container.AddStopHook(func() error {
		b.logger.Info("Shutting down event bus")
		eventBus.WaitAsync()
		return nil
	})

	return nil
}

// setupShutdownHooks configures graceful shutdown
func (b *Bootstrapper) setupShutdownHooks() {
	// Logger shutdown should be last
	b.container.AddStopHook(func() error {
		if b.logger != nil {
			b.logger.Info("Shutting down logger")
			return b.logger.Close()
		}
		return nil
	})
}

// Container returns the dependency container
func (b *Bootstrapper) Container() *container.Container {
	return b.container
}

// Shutdown gracefully shuts down all services
func (b *Bootstrapper) Shutdown() error {
	if b.logger != nil {
		b.logger.Info("Starting graceful shutdown")
	}

	if err := b.container.Stop(); err != nil {
		if b.logger != nil {
			b.logger.Error("Error during shutdown", "error", err)
		}
		return err
	}

	return nil
}

// initCompatibilityLayer initializes backward compatibility layer
func (b *Bootstrapper) initCompatibilityLayer() {
	providers.InitGlobalContainer(b.container)
	providers.InitDb()
	providers.InitRedis()
	providers.InitLogger()
	providers.InitEvent()
}